import ast
import importlib
import json
import os
import re
import shutil
import subprocess
import sys
import threading
import traceback
import zipfile

import requests
from io import StringIO
from urllib.parse import urlparse

from lib.lib import Config
from lib.command import command
from lib.var import static, loaded_plugins, threads, static_threads

config = Config("core")
anti_hack = re.compile(r"[\.\w-]+ *(?:[~=]{2})? *[\.\w]*")


class CustomJsonEncoder(json.JSONEncoder):
    def default(self, obj):
        try:
            return json.JSONEncoder.default(self, obj)
        except TypeError:
            return f"<{obj.__class__.__module__}.{obj.__class__.__name__} object at {hex(id(obj))}>"


def run_command_with_return(message):
    try:
        cmd_parts = message.strip().split()
        if not cmd_parts:
            return "错误: 空输入\n"

        old_stdout = sys.stdout
        old_stderr = sys.stderr
        sys.stdout = captured_stdout = StringIO()
        sys.stderr = captured_stderr = StringIO()

        command(cmd_parts)

        stdout = captured_stdout.getvalue()
        stderr = captured_stderr.getvalue()
        sys.stdout = old_stdout
        sys.stderr = old_stderr

        if stderr:
            return f"错误: {stderr}"
        elif stdout:
            return stdout
        else:
            return "命令已执行（无输出）\n"

    except Exception as e:
        return f"命令执行错误: {str(e)}\n"


def get_info(module: str):
    try:
        with open(module, "r", encoding="utf-8") as f:
            code = f.read()
        tree = ast.parse(code)
    except (IOError, SyntaxError):
        return None
    for node in ast.walk(tree):
        if isinstance(node, ast.Assign):
            if len(node.targets) == 1:
                target = node.targets[0]
                if isinstance(target, ast.Name) and target.id == 'Info':
                    try:
                        value = ast.literal_eval(node.value)
                        if isinstance(value, dict):
                            return value
                        else:
                            continue
                    except ValueError:
                        continue
    return None


def download_file(url, folder_path='./'):
    if not static["online"]:
        return -1
    try:
        os.makedirs(folder_path, exist_ok=True)
    except OSError as e:
        print(f"错误：无法创建文件夹 '{folder_path}'。原因: {e}")
        return None
    parsed_url = urlparse(url)
    filename = os.path.basename(parsed_url.path)
    if not filename:
        try:
            with requests.get(url, stream=True) as r:
                if "content-disposition" in r.headers:
                    cd = r.headers['content-disposition']
                    filename = cd.split('filename=')[-1].strip().strip('"')
                else:
                    filename = "downloaded_file"
                    print(f"警告：无法从 URL 或响应头确定文件名，将使用 '{filename}'")
        except requests.exceptions.RequestException as e:
            print(f"错误：下载前检查文件名时网络请求失败: {e}")
            return None
    file_path = os.path.join(folder_path, filename)
    try:
        print(f"开始下载: {url}")
        with requests.get(url, stream=True, timeout=15) as r:
            r.raise_for_status()
            with open(file_path, 'wb') as f:
                for chunk in r.iter_content(chunk_size=8192):
                    f.write(chunk)
        print(f"下载成功！文件已保存到: {file_path}")
        return filename
    except requests.exceptions.RequestException as e:
        print(f"错误：下载过程中发生网络错误: {e}")
    except IOError as e:
        print(f"错误：无法写入文件 '{file_path}': {e}")
    if os.path.exists(file_path):
        os.remove(file_path)
    return None


def is_valid_url(url: str) -> bool:
    try:
        result = urlparse(url)
        return all([result.scheme, result.netloc])
    except ValueError:
        return False


def install_pip(package, mirror: str = config.conf['pip_mirror']):
    if not static["online"]:
        return -1
    if isinstance(package, list):
        package = "\n".join(package)
    pkgs = re.findall(anti_hack, package)
    pkgs = ' '.join(pkgs)
    print(f"正在尝试安装：{pkgs}")
    if mirror:
        mirror = '-i ' + mirror
    else:
        mirror = ''
    pip_cmd = f'{static["PY_EXEC"]} -m pip install {mirror} '
    return subprocess.run(
        [pip_cmd, pkgs],
        check=True,
        capture_output=True,
        text=True
    )


def install_linux(package):
    if not static["online"]:
        return -1
    pkgs = re.findall(anti_hack, package)
    pkgs = ' '.join(pkgs)
    print(f"正在尝试安装：{pkgs}")
    return subprocess.run(
        ["apt", "install", "-y", pkgs],
        check=True,
        capture_output=True,
        text=True
    )


def git_pull(path='./'):
    if not (static["online"] and static["git_available"]):
        return -1
    if not os.path.isdir(path):
        print(f"错误: 目录 '{path}' 不存在。")
        return -1
    print(f"正在尝试更新：{path}")
    return subprocess.run(["git", "pull"],
                          cwd=path,
                          check=True,
                          capture_output=True,
                          text=True
                          ).stdout.strip()


def check_git():
    git_path = shutil.which('git')
    if git_path:
        static["git_available"] = True
        return git_path

    local_git_path = os.path.abspath("./Git/cmd/git")
    if static["SYS_INFO"] == "Windows":
        if os.path.exists(local_git_path):
            os.environ["PATH"] += os.pathsep + os.path.dirname(local_git_path)
            static["git_available"] = True
            return local_git_path

    static["git_available"] = False
    return False


def install_git():
    if not static["online"]:
        return False
    url = config.conf["portable_git"]
    zip_filename = "Git.zip"
    extract_folder = "./"
    try:
        with requests.get(url, stream=True) as r:
            r.raise_for_status()
            with open(zip_filename, 'wb') as f:
                shutil.copyfileobj(r.raw, f)
        with zipfile.ZipFile(zip_filename, 'r') as zip_ref:
            zip_ref.extractall(extract_folder)
    except Exception as e:
        return False
    finally:
        if os.path.exists(zip_filename):
            os.remove(zip_filename)
        local_git_path = os.path.abspath("./Git/cmd/git.exe")
        os.environ["PATH"] += os.pathsep + os.path.dirname(local_git_path)
        static["git_available"] = True
        return local_git_path


def load_module(module_path, f_name, ttl: int = 1):
    try:
        loaded_plugins[f_name] = importlib.import_module(f"{module_path}.{f_name}")
    except (ModuleNotFoundError, ImportError) as e:
        try:
            if not static["online"]:
                print("离线模式无法自动更新依赖")
                return -1
            print(e)
            if ttl == 0:
                return -1
            info = get_info(f"{module_path}/{f_name}.py")
            pkgs = info["pip_dependencies"]
            try:
                for_linux = info["linux_dependencies"]
                if for_linux and static["SYS_INFO"] == "Linux":
                    install_linux(for_linux)
            except KeyError:
                pass
            install_pip(pkgs)
            return load_module(module_path, f_name, ttl - 1)
        except Exception as e:
            print(f"Exception fixing import: {e}")
            return -1
    except Exception as e:
        print(f"Exception loading plugin {f_name}: {e}")
        traceback.print_exc()
        return -1
    try:
        t_name = loaded_plugins[f_name].Info["id"]
        static["running"][t_name] = False
        threads[t_name] = threading.Thread(target=loaded_plugins[f_name].__init__,
                                           name=t_name, daemon=True)
        threads[t_name].start()
        return threads[t_name]
    except Exception as e:
        print(f"Exception running module {f_name}: {e}")
        traceback.print_exc()
        return -1


def load_static(static_path, f_name, ttl: int = 1):
    try:
        loaded_plugins[f_name] = importlib.import_module(f"{static_path}.{f_name}")
    except (ModuleNotFoundError, ImportError) as e:
        try:
            if not static["online"]:
                print("离线模式无法自动更新依赖")
                return -1
            print(e)
            if ttl == 0:
                return -1
            info = get_info(f"{static_path}/{f_name}.py")
            pkgs = info["pip_dependencies"]
            try:
                for_linux = info["linux_dependencies"]
                if for_linux and static["SYS_INFO"] == "Linux":
                    install_linux(for_linux)
            except KeyError:
                pass
            install_pip(pkgs)
            return load_static(static_path, f_name, ttl - 1)
        except Exception as e:
            print(f"Exception fixing import: {e}")
            return -1
    except Exception as e:
        print(f"Exception loading plugin {f_name}: {e}")
        traceback.print_exc()
        return -1
    try:
        t_name = loaded_plugins[f_name].Info["id"]
        static_threads[t_name] = threading.Thread(target=loaded_plugins[f_name].__init__, name=t_name, daemon=True)
        return static_threads[t_name].start()
    except Exception as e:
        print(f"Exception running module {f_name}: {e}")
        traceback.print_exc()
        return -1
